package com.sfjswl.fp.core.builder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sfjswl.fp.common.dto.RuleConfDto;
import com.sfjswl.fp.common.dto.RuleInfoDto;
import com.sfjswl.fp.common.enums.NodeTypeEnum;
import com.sfjswl.fp.common.enums.TimeTypeEnum;
import com.sfjswl.fp.common.utils.JacksonUtils;
import com.sfjswl.fp.core.base.BaseLeaf;
import com.sfjswl.fp.core.base.BaseNode;
import com.sfjswl.fp.core.base.BaseRelation;
import com.sfjswl.fp.core.handler.RuleHandler;
import com.sfjswl.fp.core.leaf.base.BaseLeafFlow;
import com.sfjswl.fp.core.leaf.base.BaseLeafNone;
import com.sfjswl.fp.core.leaf.base.BaseLeafResult;
import com.sfjswl.fp.core.relation.*;
import com.sfjswl.fp.core.relation.parallel.*;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public final class RuleHandlerBuilder {

    private static final String REGEX_COMMA = ",";

    public static RuleHandler build(RuleInfoDto base) {
        final Map<Long, Set<Long>> parentIdsMap = new ConcurrentHashMap<>();
        Collection<RuleConfDto> ruleConfDtos = base.getRuleConfDtos();
        Map<Long, BaseNode> tmpConfMap = new HashMap<>(ruleConfDtos.size());
        for (RuleConfDto confDto : ruleConfDtos) {
            try {
                tmpConfMap.put(confDto.getId(), convert(confDto));
            } catch (ClassNotFoundException ce) {
                String errorNodeStr = JacksonUtils.toJsonString(confDto);
                log.error("class not found, conf:{}", errorNodeStr);
            } catch (JsonProcessingException je) {
                String errorNodeStr = JacksonUtils.toJsonString(confDto);
                log.error("json parse error, conf:{}", errorNodeStr);
            } catch (Exception e) {
                String errorNodeStr = JacksonUtils.toJsonString(confDto);
                log.error("node init error, conf:{} e:{}", errorNodeStr, e);
            }
        }
        for (RuleConfDto confInfo : ruleConfDtos) {
            if (NodeTypeEnum.isRelation(confInfo.getType())) {
                if (confInfo.getSonIds() != null && !confInfo.getSonIds().isEmpty()) {
                    Arrays.stream(confInfo.getSonIds().split(REGEX_COMMA))
                            .forEach(sonIdStr -> {
                                Long sonId = Long.valueOf(sonIdStr);
                                Set<Long> parentIds = parentIdsMap.get(sonId);
                                if (parentIds == null || parentIds.isEmpty()) {
                                    parentIds = new HashSet<>();
                                    parentIdsMap.put(sonId, parentIds);
                                }
                                parentIds.add(confInfo.getId());
                                BaseNode tmpNode = tmpConfMap.get(sonId);
                                if (tmpNode == null) {
                                    String errorModeStr = JacksonUtils.toJsonString(confInfo);
                                    log.error("sonId:{} not exist please check! conf:{}", sonId, errorModeStr);
                                } else {
                                    ((BaseRelation) tmpConfMap.get(confInfo.getId())).getChildren().add(tmpNode);
                                }
                            });
                }
            }
        }
        Long confId = base.getConfId();
        if (confId == null) {
            return null;
        }
        BaseNode root = tmpConfMap.get(confId);
        if (root == null) {
            String errorModeStr = JacksonUtils.toJsonString(base);
            log.error("confId:{} not exist please check! conf:{}", confId, errorModeStr);
            return null;
        }
        RuleHandler handler = new RuleHandler();
        handler.setRuleId(base.getId());
        handler.setTimeTypeEnum(TimeTypeEnum.getEnumDefaultNone(base.getTimeType()));
        handler.setStart(base.getStart() == null ? 0 : base.getStart());
        handler.setEnd(base.getEnd() == null ? 0 : base.getEnd());
        handler.setRoot(root);
        return handler;
    }

    private static BaseNode convert(RuleConfDto confDto) throws ClassNotFoundException, JsonProcessingException {
        BaseNode node;
        switch (NodeTypeEnum.getEnum(confDto.getType())) {
            case LEAF_FLOW:
                String flowFiled = confDto.getConfField() == null || confDto.getConfField().isEmpty() ? "{}" :
                        confDto.getConfField();
                node = (BaseLeafFlow) JacksonUtils.readJson(flowFiled, Class.forName(confDto.getConfName()));
                node.setRuleLogName(node.getClass().getSimpleName());
                ((BaseLeaf) node).afterPropertiesSet();
                break;
            case LEAF_RESULT:
                String resultFiled = confDto.getConfField() == null || confDto.getConfField().isEmpty() ? "{}" :
                        confDto.getConfField();
                node = (BaseLeafResult) JacksonUtils.readJson(resultFiled, Class.forName(confDto.getConfName()));
                node.setRuleLogName(node.getClass().getSimpleName());
                ((BaseLeaf) node).afterPropertiesSet();
                break;
            case LEAF_NONE:
                String noneFiled = confDto.getConfField() == null || confDto.getConfField().isEmpty() ? "{}" :
                        confDto.getConfField();
                node = (BaseLeafNone) JacksonUtils.readJson(noneFiled, Class.forName(confDto.getConfName()));
                node.setRuleLogName(node.getClass().getSimpleName());
                ((BaseLeaf) node).afterPropertiesSet();
                break;
            case NONE:
                node = new None();
                node.setRuleLogName("None");
                break;
            case AND:
                node = new And();
                node.setRuleLogName("And");
                break;
            case TRUE:
                node = new True();
                node.setRuleLogName("True");
                break;
            case ALL:
                node = new All();
                node.setRuleLogName("All");
                break;
            case ANY:
                node = new Any();
                node.setRuleLogName("Any");
                break;
            case P_ALL:
                node = new ParallelAll();
                node.setRuleLogName("P_All");
                break;
            case P_AND:
                node = new ParallelAnd();
                node.setRuleLogName("P_And");
                break;
            case P_ANY:
                node = new ParallelAny();
                node.setRuleLogName("P_Any");
                break;
            case P_NONE:
                node = new ParallelNone();
                node.setRuleLogName("P_None");
                break;
            case P_TRUE:
                node = new ParallelTrue();
                node.setRuleLogName("P-True");
                break;
            default:
                node = (BaseNode) JacksonUtils.readJson(confDto.getConfField() == null || confDto.getConfField().isEmpty() ? "{}" :
                        confDto.getConfField(), Class.forName(confDto.getConfName()));
                if (node != null) {
                    node.setRuleLogName(node.getClass().getSimpleName());
                }
                if (node instanceof BaseLeaf) {
                    ((BaseLeaf) node).afterPropertiesSet();
                }
                break;
        }
        node.setRuleNodeId(confDto.getId());
        node.setRuleTimeTypeEnum(TimeTypeEnum.getEnumDefaultNone(confDto.getTimeType()));
        node.setRuleStart(confDto.getStart() == null ? 0 : confDto.getStart());
        node.setRuleEnd(confDto.getEnd() == null ? 0 : confDto.getEnd());
        return node;
    }
}
